/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package hextd.tower;

import hextd.hex.Hex;
import hextd.creep.Factory;
import hextd.core.Context;
import hextd.core.ContextPlayer;
import hextd.util.Geometry;
import hextd.util.ParameterUtils;
import hextd.util.SiFormatter;
import hextd.wave.Path;
import hextd.wave.Wave;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.GeneralPath;
import java.util.Collection;
import java.util.ArrayList;

/**
 *
 * @author hylke
 */
public class TowerHome extends TowerBase {

	public static int price = 1000;
	public static int damage = 0;
	public static float range = 0f;
	private Color transColor;
	//
	protected int moneyTimerStart = 100;
	private int moneyTimer = moneyTimerStart;
	protected long income = 1;
	//
	private int spawnPool = 0;
	protected int spawnPoolMax = 10;
	private int spawnPoolTimerStart = 50;
	private int spawnPoolTimer = spawnPoolTimerStart;
	private Font spawnPoolFont;
	protected float[] spawnPoolLocation = {0f, 0f};
	//
	private int spawnsInQueue = 0;
	//
	private final static int HHType = 6;
	private float[] incomeIncreaseFactor = {
		100f / 100,
		100f / 100,
		100f / 100,
		100f / 100,
		100f / 100,
		100f / 100,
		100f / 100,
		100f / 100,
		100f / 100};
	private int[] creepCost = {10, 10, 10, 14, 11, 12, 12, 13, 15};
	private String[] allCreepTypes = {"rr", "bb", "yr", "gp", "ss", "hg", "prp", "ff", "hh"};
	protected String[] curCreepTypes;
	private int maxCreepType = 3;
	private int selectedCreepType = 0;
	private String[] creepCountMode = {"10"};
	private int creepCount = 1;
	//
	private String[] creepHealths = {"100", "1 000", "10 000", "100 000", "1 M", "10 M", "100 M", "1 T", "10 T", "100 T", "1 P", "10 P", "100 P"};
	private String[] creepHealthsCur;
	protected int creepHealthsMax = 0;
	private int creepHealth = 0;
	//
	private String[] allSizes = {"N", "S", "L"};
	private String[] redSizes = {"N", "S"};
	protected String[] curSizes = allSizes;
	private int selectedSize = 0;
	//
	private ArrayList<Path> paths = new ArrayList<Path>();
	private String[] pathStrings = {};
	private int selectedPath = 0;
	//
	private PanelTowerSettingsHomeBase p;

	protected class TowerActionSendCreep extends TowerAction {

		public int type;
		public int count;
		public int size;
		public int path;
		public int health;

		public TowerActionSendCreep(int gameTime, int type, int count, int size, int path, int health) {
			super(gameTime, "creep");
			this.type = type;
			this.count = count;
			this.size = size;
			this.path = path;
			this.health = health;
		}
	}

	public TowerHome(Context context, Hex hex, int owner, int activateTick) {
		super(TowerFactory.type.homeBase, price, damage, range, owner, activateTick);
		this.lineColor = new Color(255, 255, 255);
		this.transColor = new Color(this.lineColor.getRed(), this.lineColor.getGreen(), this.lineColor.getBlue(), 75);
		this.setCreeps();
		super.doInit(context, hex);
	}

	@Override
	protected void makeButtons() {
		p = new PanelTowerSettingsHomeBase(this, cn);
		p.setModes(1, this.curCreepTypes);
		p.setModes(2, this.curSizes);
		p.setModes(3, this.creepCountMode);
		p.setModes(4, this.pathStrings);
		p.setModes(5, this.creepHealthsCur);
		pts = p;
	}

	private void setCreeps() {
		this.curCreepTypes = new String[this.maxCreepType + 1];
		System.arraycopy(this.allCreepTypes, 0, this.curCreepTypes, 0, this.maxCreepType + 1);

		this.creepHealthsCur = new String[this.creepHealthsMax + 1];
		System.arraycopy(this.creepHealths, 0, this.creepHealthsCur, 0, this.creepHealthsMax + 1);
		if (p != null) {
			p.setModes(1, this.curCreepTypes);
			p.setModes(5, this.creepHealthsCur);
		}
	}

	@Override
	protected void handleAction(TowerAction a) {
		if (a.action.equals("creep")) {
			TowerActionSendCreep asc = (TowerActionSendCreep) a;
			if (!this.runSendCreeps(asc.type, asc.count, asc.size, asc.path, asc.health)) {
				System.err.println("TowerHome::handleAction: Sending creeps failed.");
				System.err.println("TowerHome::handleAction: " + asc.type + ", " + asc.count + ", " + asc.size + ", " + asc.path + ", " + asc.health + ".");
				System.err.println("TowerHome::handleAction: " + this.maxCreepType + ", " + this.spawnPool + "-" + this.spawnsInQueue + ".");
			}
			return;
		}
		super.handleAction(a);
	}

	public synchronized void update() {
		Collection<Path> newPaths = this.context.getPaths(this.getOwner());
		this.paths.clear();
		this.paths.addAll(newPaths);
		this.pathStrings = new String[newPaths.size()];
		for (int i = 0; i < this.pathStrings.length; i++) {
			this.pathStrings[i] = "Path " + (i + 1);
		}
		if (pts != null) {
			this.pts.setModes(4, this.pathStrings);
		}
	}

	public synchronized void trySendCreeps() {
		int tempCreepCount = creepCount;
		if (tempCreepCount > spawnPool - spawnsInQueue) {
			return;
		}
		if (tempCreepCount < 1) {
			return;
		}
		if (selectedCreepType > maxCreepType) {
			selectedCreepType = maxCreepType;
		}
		if (selectedCreepType == HHType && selectedSize == 2) {
			selectedSize = 0;
		}
		if (selectedPath >= paths.size()) {
			selectedPath = 0;
		}

		//System.out.println("TowerHome::trySendCreeps: count=" + this.creepCount + " pool=" + this.spawnPool + " queue=" + this.spawnsInQueue);

		long costs = getCreepPrice(creepHealth, selectedCreepType) * creepCount;
		ContextPlayer player = context.getPlayer(getOwner());
		if (player.canPay(costs, true)) {
			context.buyCreeps(this, selectedCreepType, tempCreepCount, selectedSize, selectedPath, creepHealth);
		}
	}

	public boolean queueSendCreeps(int activateTick, int type, int count, int size, int path, int health, boolean queue) {
		// These checks are time-independant.
		if (type == HHType && size == 2) {
			size = 0;
		}
		if (path < 0 || path >= this.paths.size()) {
			path = 0;
		}
		if (health < 0) {
			health = 0;
		}
		long costs = getCreepPrice(health, type) * count;
		if (queue) {
			ContextPlayer player = context.getPlayer(getOwner());
			synchronized (player) {
				if (!player.queuePayment(costs)) {
					return false;
				}
			}
		}
		addActionItem(new TowerActionSendCreep(activateTick, type, count, size, path, health));
		spawnsInQueue += count;

		return true;
	}

	public boolean runSendCreeps(int typeId, int count, int sizeId, int pathId, int healthId) {
		ContextPlayer player = context.getPlayer(this.getOwner());
		// These checks are time-dependant.
		if (count < 1) {
			return false;
		}
		if (typeId > maxCreepType) {
			typeId = maxCreepType;
		}
		Path path = paths.get(pathId);
		int realPathId = this.context.getPaths().indexOf(path);
		long realHealth = 100 * (long) Math.pow(10, healthId);

		long costPerCreep = getCreepPrice(realHealth, typeId);
		long incomeIncPerCreep = getIncomeIncrease(costPerCreep, typeId);
		long totalCost = costPerCreep * count;
		long totalIncomeIncrease = incomeIncPerCreep * count;

		if (count > this.spawnsInQueue) {
			System.err.println("TowerHome::runSendCreeps: " + context.getGameTime().getTime() + " User " + this.getOwner() + " sending creeps that are not in queue: " + count + " > " + this.spawnsInQueue + "!");
		}
		//System.out.println("TowerHome::queueSendCreeps: Buying health " + realHealth + " for " + costs);

		int targetId = path.getTargetUser();
		ContextPlayer tp = context.getPlayer(targetId);
		if (!tp.isActive()) {
			player.unQueuePayment(totalCost);
			//System.out.println("TowerHome::runSendCreeps: " + context.getGameTime().getTime() + " User " + this.getOwner() + " is sendig creeps to a dead player...");
			return false;
		}

		this.spawnPool -= count;
		this.spawnsInQueue -= count;
		if (player.doPay(totalCost)) {
			String[] enemies = new String[2];
			enemies[0] = "" + count;
			long rewardPerCreep = Math.max(1, costPerCreep / 4);
			enemies[1] = "" + this.allSizes[sizeId] + this.allCreepTypes[typeId];
			Wave newWave = new Wave(realHealth, rewardPerCreep, 0, this.getOwner());
			newWave.setContext(context);
			newWave.addEnemiesFromNames(enemies, realPathId);
			this.context.addEnemies(newWave.getEnemies());
			this.income += totalIncomeIncrease;
			return true;
		} else {
			System.err.println("TowerHome::runSendCreeps: " + context.getGameTime().getTime() + " User " + this.getOwner() + " " +
					"could not afford to send creeps: " + player.getCredits(true) + " (" + player.getCredits(false) + ") < " + totalCost);
			return false;
		}
	}

	private long getCreepPrice(long health, int type) {
		double power = 0.8;
		double base = (double) Math.pow(100, power);
		return Math.round(Math.pow(health, power) / base * creepCost[type]);
	}

	protected long getCreepPrice(int healthId, int type) {
		long health = 100l * (long) Math.pow(10, healthId);
		return this.getCreepPrice(health, type);
	}

	private long getIncomeIncrease(long creepPrice, int creepType) {
		return Math.round(0.5 * incomeIncreaseFactor[creepType] * Math.pow(creepPrice, 0.85));
	}

	protected void giveIncome(int gameTime) {
		ContextPlayer player = context.getPlayer(getOwner());
		player.doReceive(income);
		player.setScore(income);
		//System.out.println("TowerHome::runTick: Tick: " + gameTime + " Income for player " + this.getOwner() + " = " + this.income);
	}

	@Override
	public void runTick(int gameTime) {
		this.moneyTimer--;
		if (this.moneyTimer <= 0) {
			this.moneyTimer = this.moneyTimerStart;
			giveIncome(gameTime);
		}

		if (this.spawnPool < this.spawnPoolMax) {
			this.spawnPoolTimer--;
			if (this.spawnPoolTimer <= 0) {
				this.spawnPoolTimer = this.spawnPoolTimerStart;
				this.spawnPool++;
			}

		}
	}

	@Override
	public int getUpgradePrice(boolean future) {
		if (future) {
			if (level + levelInQue >= levelMax) {
				return -1;
			}
			return (int) Math.round(0.5f * TowerHome.price * Math.pow(4, level + levelInQue));
		} else {
			if (level >= levelMax) {
				return -1;
			}
			return (int) Math.round(0.5f * TowerHome.price * Math.pow(4, level));
		}
	}

	@Override
	protected void runUpgrade() {
		ContextPlayer player = context.getPlayer(getOwner());
		if (player.doPay(getUpgradePrice(false))) {
			player.doReceive(0);
			level++;
			levelInQue--;
			spawnPoolMax += 10;
			creepCountMode[0] = "" + spawnPoolMax;
			if (pts != null) {
				pts.setModes(3, creepCountMode);
			}

			spawnPoolTimerStart -= 5;
			creepHealthsMax = Math.min(creepHealthsMax + 2, creepHealths.length - 1);
			if (level > 2) {
				maxCreepType = 5;
			}

			if (level > 4) {
				maxCreepType = 8;
			}

			setCreeps();
			fireTowerChangedEvent();
		} else {
			System.err.println("TowerHome::runUpgrade: " + context.getGameTime() + " User " + getOwner() + " can not upgrade tower at " + hex.getLocation() + ".");
		}
	}

	@Override
	protected boolean runMode(int m, int v) {
		if (m == 1) {
			v = ParameterUtils.forceIndexBounds(curCreepTypes, v);
			this.selectedCreepType = v;
			if (v == HHType && curSizes != redSizes) {
				curSizes = redSizes;
				pts.setModes(2, curSizes);
			}

			if (v != HHType && curSizes != allSizes) {
				curSizes = allSizes;
				pts.setModes(2, curSizes);

			}

		} else if (m == 2) {
			ParameterUtils.forceIndexBounds(curSizes, v);
			if (selectedCreepType == HHType && v == 2) {
				v = 0;
			}
			selectedSize = v;

		} else if (m == 3) {
			if (v > spawnPoolMax) {
				v = spawnPoolMax;
			}
			if (v < 1) {
				v = 1;
			}
			creepCount = v;

		} else if (m == 4) {
			ParameterUtils.forceIndexBounds(pathStrings, v);
			if (v >= 0 && v < pathStrings.length) {
				Path path = paths.get(selectedPath);
				path.setSelected(false);
				selectedPath = v;
				path = paths.get(selectedPath);
				path.setSelected(true);
			}

		} else if (m == 5) {
			ParameterUtils.forceIndexBounds(creepHealthsCur, v);
			creepHealth = v;

		}
		fireTowerChangedEvent();

		return true;
	}

	@Override
	public String getStatusString() {
		String retString = "";
		retString +=
				"<h1>Home Base</h1>" +
				"<p>" +
				"<b>Creep Menu:</b><br>" +
				"<table>\n";
		//float priceFactor = this.getCreepPrice(this.creepHealth);
		for (int i = 0; i < allCreepTypes.length; i++) {
			long tempPrice = getCreepPrice(creepHealth, i);
			long tempIncomeInc = getIncomeIncrease(tempPrice, i);
			retString += "<tr><td>" + Factory.identifyEnemy(this.allCreepTypes[i]).toString() + ": </td>" +
					"<td class='nr'>+" + SiFormatter.format(tempIncomeInc) + "€</td>" +
					"<td class='nr'>" + SiFormatter.format(tempPrice) + "€</td></tr>\n";
		}

		retString += "</table>";
		retString += "<b>Creep Pool:</b> " + this.spawnPool + "/" + this.spawnPoolMax + "<br>\n";
		retString += "<b>Income:</b> " + SiFormatter.format(this.income) + "€<br>\n";
		retString += "</p>\n";
		long tempCosts = getCreepPrice(creepHealth, selectedCreepType) * creepCount;
		retString += "<p><br/><b>Current selection costs:</b><br/>" + SiFormatter.format(tempCosts) +
				"€</p>";
		return retString;
	}

	public long getCosts() {
		return getCreepPrice(creepHealth, selectedCreepType) * creepCount;
	}

	@Override
	public String getInfoString() {
		String retString = "";
		retString +=
				"<h1>Home Base</h1>" +
				"<p>" +
				"Defend this!.<br>" +
				"</p>";
		return retString;
	}

	@Override
	public void resetScale() {
		super.resetScale();
		this.update();
	}

	@Override
	protected void generateShapes() {
		int shapeCount = 2;
		int i;
		float[][] points = Geometry.createHexCoords(this.paintScale);
		float[][] points2 = Geometry.createHexCoords(this.paintScale * 0.5f);

		this.shapes = new Shape[shapeCount];
		GeneralPath poly = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
		poly.append(new CubicCurve2D.Float(points[0][0], points[0][1], points2[0][ 0], points2[0][1], points2[1][0], points2[1][1], points[1][ 0], points[1][ 1]), true);
		poly.append(new CubicCurve2D.Float(points[1][0], points[1][1], points2[1][ 0], points2[1][1], points2[2][0], points2[2][1], points[2][ 0], points[2][ 1]), true);
		poly.append(new CubicCurve2D.Float(points[2][0], points[2][1], points2[2][ 0], points2[2][1], points2[3][0], points2[3][1], points[3][ 0], points[3][ 1]), true);
		poly.append(new CubicCurve2D.Float(points[3][0], points[3][1], points2[3][ 0], points2[3][1], points2[4][0], points2[4][1], points[4][0], points[4][1]), false);
		poly.append(new CubicCurve2D.Float(points[4][0], points[4][1], points2[4][ 0], points2[4][1], points2[5][0], points2[5][1], points[5][ 0], points[5][ 1]), true);
		poly.append(new CubicCurve2D.Float(points[5][0], points[5][1], points2[5][ 0], points2[5][1], points2[0][0], points2[0][1], points[0][0], points[0][1]), true);
		//poly.append(new Ellipse2D.Float(-0.6f * this.paintScale, -0.6f * this.paintScale, 1.2f * this.paintScale, 1.2f * this.paintScale), false);

		i = 0;
		this.shapes[i++] = new Area(poly);
		this.shapes[i++] = this.shapes[0];
		//this.shapes[i++] = new Ellipse2D.Float(-1.0f * strokeScale, -1.0f * strokeScale, 2.0f * strokeScale, 2.0f * strokeScale);

		i = 0;
		this.shapeAction = new int[shapeCount];
		this.shapeAction[i++] = 2;
		this.shapeAction[i++] = 1;
		//this.shapeAction[i++] = 1;

		i = 0;
		this.shapeColor = new Color[shapeCount];
		this.shapeColor[i++] = this.transColor;
		this.shapeColor[i++] = this.lineColor;
		//this.shapeColor[i++] = this.lineColor;

		i = 0;
		this.strokes = new Stroke[shapeCount];
		this.strokes[i++] = new BasicStroke(2.0f * strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
		this.strokes[i++] = this.strokes[0];
		//this.strokes[i++] = this.strokes[0];
		this.spawnPoolFont = new Font(Font.DIALOG, Font.PLAIN, (int) (0.80 * this.paintScale));
		this.spawnPoolLocation[0] = -0.5f * hexPaintWidth;
		this.spawnPoolLocation[1] = 0.3f * this.paintScale;

	}

	@Override
	public void paint(Graphics2D g2, int gameTime) {
	}

	@Override
	public void paintEffects(Graphics2D g2, int gameTime) {
		super.paint(g2, gameTime);
		AffineTransform saveXform = g2.getTransform();

		g2.transform(atTranslate);
		g2.setColor(this.lineColor);
		g2.setFont(this.spawnPoolFont);
		g2.drawString("" + (spawnPool - spawnsInQueue), this.spawnPoolLocation[0], this.spawnPoolLocation[1]);

		g2.setFont(this.levelFont);
		g2.drawString("" + (this.level + 1), this.levelLocation[0], this.levelLocation[1]);

		g2.setTransform(saveXform);
	}

	@Override
	public boolean canSell() {
		return false;
	}

	public int getSpawnPoolSize() {
		return spawnPool - spawnsInQueue;
	}
}
